package net.kucoe.grin.context;

import java.util.*;
import java.util.logging.Logger;

import net.kucoe.grin.Grin;
import net.kucoe.grin.System;
import net.kucoe.grin.context.application.InitArgument;
import net.kucoe.grin.internal.InputInitialActor;

/**
 * Describes wired {@link System}.
 * 
 * @author Vitaliy Basyuk
 */
public class ContextSystem extends ContextWired {
    
    private static final Logger logger = Grin.getLogger("System");
    
    private final Map<String, ContextActor> actors;
    private final Map<String, ContextSpace> spaces;
    private final Map<String, ContextResult> results;
    private final Map<String, ContextStrategy> strategies;
    private final ContextActor inputActor;
    private Class<?> initialResult;
    private List<InitArgument> arguments;
    
    /**
     * Constructs ContextSystem.
     * 
     * @param wired
     * @param actors
     * @param spaces
     * @param results
     * @param strategies
     * @param inputActor
     */
    public ContextSystem(final Object wired, final Map<String, ContextActor> actors,
            final Map<String, ContextSpace> spaces, final Map<String, ContextResult> results,
            final Map<String, ContextStrategy> strategies, final ContextActor inputActor) {
        super(wired);
        this.actors = new HashMap<String, ContextActor>(actors);
        this.spaces = new HashMap<String, ContextSpace>(spaces);
        this.results = new HashMap<String, ContextResult>(results);
        this.strategies = new HashMap<String, ContextStrategy>(strategies);
        this.inputActor = inputActor;
    }
    
    /**
     * Returns the actors Map<String,ContextActor>.
     * 
     * @return the actors Map<String,ContextActor>.
     */
    public Map<String, ContextActor> getActors() {
        return actors;
    }
    
    /**
     * Returns the spaces Map<String,ContextSpace>.
     * 
     * @return the spaces Map<String,ContextSpace>.
     */
    public Map<String, ContextSpace> getSpaces() {
        return spaces;
    }
    
    /**
     * Returns the results Map<String,ContextResult>.
     * 
     * @return the results Map<String,ContextResult>.
     */
    public Map<String, ContextResult> getResults() {
        return results;
    }
    
    /**
     * Returns the strategies Map<String,ContextStrategy>.
     * 
     * @return the strategies Map<String,ContextStrategy>.
     */
    public Map<String, ContextStrategy> getStrategies() {
        return strategies;
    }
    
    /**
     * Returns the inputActor ContextActor.
     * 
     * @return the inputActor ContextActor.
     */
    public ContextActor getInputActor() {
        return inputActor;
    }
    
    /**
     * Returns the initialResult Class<?>.
     * 
     * @return the initialResult Class<?>.
     */
    public Class<?> getInitialResult() {
        return initialResult;
    }
    
    /**
     * Overrides initialResult the initialResult.
     * 
     * @param initialResult the initialResult to set.
     */
    public void setInitialResult(final Class<?> initialResult) {
        this.initialResult = initialResult;
    }
    
    /**
     * Returns the arguments List<InitArgument>.
     * 
     * @return the arguments List<InitArgument>.
     */
    public List<InitArgument> getArguments() {
        return arguments;
    }
    
    /**
     * Overrides arguments the arguments.
     * 
     * @param arguments the arguments to set.
     */
    public void setArguments(final List<InitArgument> arguments) {
        this.arguments = arguments;
    }
    
    /**
     * Sys Init
     */
    public void initializeSystem() {
        if (initialResult != null) {
            if (arguments != null) {
                Map<String, Object> map = new HashMap<String, Object>();
                for (InitArgument argument : arguments) {
                    // TODO process references
                    map.put(argument.getName(), argument.getValue());
                    setInputArguments(map);
                }
            }
            produce(initialResult);
        }
    }
    
    private void setInputArguments(final Map<String, Object> arguments) {
        ((InputInitialActor) inputActor.getWired()).setArguments(arguments);
    }
    
    /**
     * Start producing result.
     * 
     * @param clazz result type
     * @return result
     */
    public <T> T produce(final Class<T> clazz) {
        String name = clazz.getName();
        ContextResult contextResult = results.get(name);
        List<ContextValidator> validators = new ArrayList<ContextValidator>(contextResult.getValidators().values());
        int bestValidation = 0;
        T bestResult = null;
        for (ContextActor actor : actors.values()) {
            if (actor.producable(clazz)) {
                T result = inputActor.interactFor(clazz, actor, validators);
                int validation = validate(validators, result);
                if (validation > bestValidation) {
                    bestResult = result;
                    bestValidation = validation;
                }
            }
        }
        if (bestValidation == 0) {
            logger.warning("Low quality result for class:" + name);
        }
        return bestResult;
    }
    
    private int validate(final List<ContextValidator> validators, final Object result) {
        int worstValidation = 0;
        for (ContextValidator validator : validators) {
            int validation = validator.validate(result);
            if (worstValidation == 0 || validation < worstValidation) {
                worstValidation = validation;
            }
        }
        return worstValidation;
    }
    
}
